#include "common.h"
#include "counter.h"
#include "Vcrat_wrapper.h"
#include "Vcrat_wrapper___024root.h"
#include <vector>
#include <queue>
class CRAT
{
public:
    int gc_num, prf_size;
    std::vector<int> rat;
    std::vector<bool> rat_valid;
    std::queue<std::vector<bool>> gc_buffer;
    CRAT(int gc_num, int prf_size);
    ~CRAT() = default;
    int read(int arf_idx);
    void write(int arf_idx, int prf_idx);
    void save_gc();
    void commit_gc();
    void restore_gc();
    void clear();
};
extern CRAT *debug_crat;
// #define DEBUG
#define TRACE 0
#define MAX_WAVE 1000000
#define MAX_CYCLE 1000000
#define RENAME_WIDTH 4
#define COMMIT_WIDTH 2
#define PRF_SIZE 92
Vcrat_wrapper top;
VerilatedFstC *tfp;
static uint64_t wave_cnt = 0;

typedef struct
{
    int prf_idx;
    int free_prf_idx;
    bool rd_valid;
    uint64_t cycle;
    bool restore_gc;
} ROB_Entry;

void dump()
{
#if TRACE
    if (wave_cnt < MAX_WAVE)
    {
        tfp->dump(Verilated::time());
        wave_cnt++;
    }
#endif
}
void run()
{
    top.eval();
    dump();
    Verilated::timeInc(1);

    top.clk = 0;
    top.eval();
    dump();
    Verilated::timeInc(1);

    top.eval();
    dump();
    Verilated::timeInc(1);

    top.clk = 1;
    top.eval();
    dump();
    Verilated::timeInc(1);
}
void reset()
{
    for (size_t i = 0; i < RENAME_WIDTH; i++)
    {
        top.rd_valid[i] = 0;
        top.save_gc[i] = 0;
    }

    for (size_t i = 0; i < COMMIT_WIDTH; i++)
    {
        top.commit_gc[i] = 0;
        top.restore_gc[i] = 0;
    }
    top.rst = 1;
    run();
    run();
    run();
    top.rst = 0;
}
Counter cycle("cycle");

int rat_table[32];

#include <queue>
void normal_rename_test()
{
    LOG("normal_rename_test");
    std::queue<int> freelist;

    for (size_t i = 32; i < PRF_SIZE; i++)
    {
        freelist.push(i);
    }

    reset();
    size_t test_cnt = 0;
    bool sim_finish = false;
    do
    {
        run();
        cycle++;

        for (size_t i = 0; i < RENAME_WIDTH; i++)
        {
            // rs0
            uint32_t r = rand();
            r %= 32;
            top.ars0[i] = r;
            // rs1
            r = rand();
            r %= 32;
            top.ars1[i] = r;
            // LOG("Read rs0 : %d rs1 : %d at %lu", top.ars0[i], top.ars1[i], Verilated::time());
            // rd
            r = rand();
            r %= 100;
            top.rd_valid[i] = r < 80 ? 1 : 0;

            if (top.rd_valid[i])
            {
                r = rand();
                r %= 32;
                top.ard[i] = r;
                top.allocate_prd[i] = freelist.front();
                freelist.pop();
                // LOG(COLOR("Write rd : %d at %lu", GREEN), top.ard[i], Verilated::time());
            }
        }
        top.eval();
        for (size_t i = 0; i < RENAME_WIDTH; i++)
        {
            if (top.rd_valid[i])
            {
                freelist.push(top.prd[i]);
            }
        }

    } while (!sim_finish && cycle < MAX_CYCLE);
}

void global_checkpoint_test()
{
    LOG("global_checkpoint_test");
    std::queue<int> freelist;
    std::queue<ROB_Entry> rob;
    for (size_t i = 32; i < PRF_SIZE; i++)
    {
        freelist.push(i);
    }

    reset();
    cycle = 0;
    size_t test_cnt = 0;
    bool sim_finish = false;
    // std::queue<uint64_t> rob_has_gc;
    size_t gc_num = 0;
    do
    {
        bool restore = false;
        for (size_t i = 0; i < COMMIT_WIDTH; i++)
        {
            if (top.commit_gc[i])
            {
                if (top.restore_gc[i])
                {
                    gc_num = 0;
                    // LOG(COLOR("Top commit and restore gc", YELLOW));
                    while (!rob.empty())
                    {
                        if (rob.front().rd_valid)
                        {
                            freelist.push(rob.front().prf_idx);
#ifdef DEBUG
                            LOG(COLOR("Restore Free prd : %d", BLUE), rob.front().prf_idx);
#endif
                        }
                        rob.pop();
                    }
                    restore = true;
                }
                else
                {
                    gc_num--;
                    // LOG(COLOR("Top commit gc", YELLOW));
                }
            }
        }
        for (size_t i = 0; i < RENAME_WIDTH; i++)
        {
            if (top.save_gc[i])
            {
                // LOG(COLOR("Top save gc", YELLOW));
                gc_num++;
            }
        }
        // LOG(COLOR("Top gc num %ld", YELLOW), gc_num);

        // LOG("Cycle %lu begin", (uint64_t)cycle);
        run();
        // LOG("Cycle %lu end", (uint64_t)cycle);
        cycle++;
#ifdef DEBUG
        if (restore)
        {
            LOG("RAT : ");
            for (size_t i = 0; i < 32; i++)
            {
                rat_table[i] = debug_crat->read(i);
                printf("%3d ", rat_table[i]);
                if (i % 8 == 7)
                    printf("\n");
            }
            LOG(COLOR("Freelist entry num : %ld", GREEN), freelist.size());
            size_t len = freelist.size();
            for (size_t i = 0; i < len; i++)
            {
                auto data = freelist.front();
                freelist.pop();
                freelist.push(data);
                printf("%3d ", data);
                for (size_t j = 0; j < 32; j++)
                {
                    if (rat_table[i] == data)
                    {
                        printf("\b" COLOR("X", RED));
                        sim_finish = true;
                    }
                }
            }
            printf("\n");
        }
#endif
        for (size_t i = 0; i < PRF_SIZE; i++)
        {
            // top.rootp.crat_wrapper__DOT__inst_crat__DOT__map_table_valid_pack;
            auto dut = top.rootp->crat_wrapper__DOT__inst_crat__DOT__map_table_valid[i];
            auto ref = debug_crat->rat_valid[i];
            if ((int)dut != (int)ref)
            {
                LOG(COLOR("rat_valid[%3ld] dut %d != ref %d", RED), i, (int)dut, (int)ref);
                sim_finish = true;
            }
        }

        for (size_t i = 0; i < COMMIT_WIDTH; i++)
        {
            top.commit_gc[i] = 0;
            top.restore_gc[i] = 0;
        }
        for (size_t i = 0; i < RENAME_WIDTH; i++)
        {
            top.rd_valid[i] = 0;
            top.save_gc[i] = 0;
        }
        if (!rob.empty() && rob.front().cycle + 2 < cycle)
        {
            uint32_t r = rand();
            r %= 100;
            bool commit_gc = r < 90;
            bool restore_gc = false;
            if (commit_gc)
            {
                for (size_t i = 0; i < COMMIT_WIDTH; i++)
                {
                    if (rob.empty())
                    {
                        break;
                    }
                    top.commit_gc[i] = 1; // rob.front().has_gc;
                    restore_gc = rob.front().restore_gc;
                    if (rob.front().rd_valid)
                    {
                        freelist.push(rob.front().free_prf_idx);
#ifdef DEBUG
                        LOG(COLOR("Commit Free prd : %d", GREEN), rob.front().free_prf_idx);
#endif
                    }
                    rob.pop();
                    if (restore_gc)
                    {
                        gc_num = 0;
                        top.restore_gc[i] = 1;
                        break;
                    }
                }
#ifdef DEBUG
                if (restore_gc)
                {
                    LOG(COLOR("Freelist entry num : %ld", GREEN), freelist.size());
                    size_t len = freelist.size();
                    for (size_t i = 0; i < len; i++)
                    {
                        auto data = freelist.front();
                        freelist.pop();
                        freelist.push(data);
                        printf("%3d ", data);
                    }
                    printf("\n");
                    continue;
                }
#endif
            }
        }

        for (size_t i = 0; i < 32; i++)
        {
            debug_crat->read(i);
        }

        if (freelist.size() < RENAME_WIDTH)
        {
            continue;
        }
        for (size_t i = 0; i < RENAME_WIDTH; i++)
        {
            // rs0
            uint32_t r = rand();
            r %= 32;
            top.ars0[i] = r;
            // rs1
            r = rand();
            r %= 32;
            top.ars1[i] = r;
            // LOG("Read rs0 : %d rs1 : %d at %lu", top.ars0[i], top.ars1[i], Verilated::time());
            // rd
            r = rand();
            r %= 100;
            top.rd_valid[i] = r < 80 ? 1 : 0;

            if (top.rd_valid[i])
            {
                r = rand();
                r %= 32;
                top.ard[i] = r;
                top.allocate_prd[i] = freelist.front();
                freelist.pop();
                // LOG(COLOR("Spec Write rd : %d at %lu", GREEN), top.ard[i], Verilated::time());
            }

            r = rand();
            r %= 100;
            top.save_gc[i] = 1; // r < 50 ? 1 : 0;
            if (top.save_gc[i])
            {
            }
        }
        top.eval();
        for (size_t i = 0; i < RENAME_WIDTH; i++)
        {
            ROB_Entry rob_entry;
            rob_entry.cycle = cycle;
            rob_entry.prf_idx = top.allocate_prd[i];
            rob_entry.free_prf_idx = top.prd[i];
            rob_entry.rd_valid = top.rd_valid[i];
#ifdef DEBUG
            if (top.rd_valid[i])
            {
                LOG(COLOR("Spec Write allocate_prd : %3d prd : %3d ard : %3d at %lu", YELLOW), top.allocate_prd[i], top.prd[i], top.ard[i], Verilated::time());
            }
#endif
            uint32_t r = rand();
            r %= 100;
            rob_entry.restore_gc = r < 5;
            rob.push(rob_entry);
        }

    } while (!sim_finish && cycle < MAX_CYCLE);
}

int main()
{
#if TRACE
    Verilated::traceEverOn(true);
    tfp = new VerilatedFstC();
    top.trace(tfp, 0);
    tfp->open("Vcrat_wrapper.fst");
#endif
#ifdef VL_DEBUG
    LOG("VL_DEBUG Enable");
    Verilated::debug(1);
#endif
    srand(0);
    Verilated::randSeed(0);
    Verilated::randReset(0);
    reset();
    // normal_rename_test();
    global_checkpoint_test();
    // Final model cleanup
    top.final();

#if TRACE
    tfp->close();
    delete tfp;
#endif
    LOG("All test done");
    return 0;
}